﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.IO;
using DeepTalkDBServiceTest.DTDBService;
using IndicoInterface;
using DeepTalkDBServiceTest.ServiceReference1;
using System.ServiceModel;
using System.Threading;

namespace DeepTalkDBServiceTest
{
    /// <summary>
    /// Summary description for t_RenderingTalks
    /// </summary>
    [TestClass]
    public class t_RenderingTalks
    {
        public t_RenderingTalks()
        {
        }

        private TestContext testContextInstance;

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        #region Additional test attributes
        //
        // You can use the following additional attributes as you write your tests:
        //
        // Use ClassInitialize to run code before running the first test in the class
        // [ClassInitialize()]
        // public static void MyClassInitialize(TestContext testContext) { }
        //
        // Use ClassCleanup to run code after all tests in a class have run
        // [ClassCleanup()]
        // public static void MyClassCleanup() { }
        //
        // Use TestInitialize to run code before running each test 
        // [TestInitialize()]
        // public void MyTestInitialize() { }
        //
        // Use TestCleanup to run code after each test has run
        // [TestCleanup()]
        // public void MyTestCleanup() { }
        //
        #endregion

        [TestInitialize]
        public void InitTest()
        {
            FileInfo db = new FileInfo("ConfData_" + TestContext.TestName + ".sdf");
            File.Copy(@"..\..\..\DeepTalkDBService\ConferenceData.sdf", db.FullName);
            using (DTDBClient rc = new DTDBClient())
            {
                rc.SetDBFile(db);
            }
        }

        [TestMethod]
        public void TestNullRendered()
        {
            using (DTDBClient rc = new DTDBClient())
            {
                BasicTalkInfo[] talks = rc.GetRenderedTalks(10);
                Assert.IsTrue(talks.Length == 0, "No talks inserted, should be no rendered talks!");
                rc.SubmitNewTalk(GetTalkXML(), "talk 1", DateTime.Now);
                talks = rc.GetRenderedTalks(10);
                Assert.IsTrue(talks.Length == 0, "Added talks, but should still be none rendered!");
            }
        }

        string GetTalkXML(string id)
        {
            /// Submit a talk for rendering -- should not crash! :-)

            AgendaInfo info = new AgendaInfo("http://indico.cern.ch/conferenceDisplay.py?showSession=all&showDate=all&view=xml&fr=no&confId=" + id);
            StringWriter sr = new StringWriter();
            info.Seralize(sr);
            return sr.ToString();
        }

        string GetTalkXML()
        {
            return GetTalkXML("a042880");
        }

        class callbackWatcher : ServiceReference1.IDTRenderCallback
        {
            public bool WasCalled { get; set; }
            public AutoResetEvent evt = new AutoResetEvent(false);

            int _count = 0;

            public int Count
            {
                get { return _count; }
            }

            public void Reset()
            {
                evt.Reset();
            }

            public bool WaitOnEvent(int ticksToTimeOut)
            {
                return evt.WaitOne(ticksToTimeOut);
            }

            public callbackWatcher()
            {
                WasCalled = false;
            }

            public bool RenderTalk(DeepTalkDBServiceTest.ServiceReference1.DTRenderTalkInfo info)
            {
                WasCalled = true;
                _count = _count + 1;
                evt.Set();
                return true;
            }
        }

        [TestMethod]
        public void TestNoRenderCallback()
        {
            /// Get render info back, but make sure that nothing
            /// actually gets back

            callbackWatcher cbw = new callbackWatcher();
            using (DTRenderClient rc = new DTRenderClient(new InstanceContext(cbw)))
            {
                Thread.Sleep(1000);
            }
            Assert.IsFalse(cbw.WasCalled, "Seems like the callback was called!");
        }

        [TestMethod]
        public void TestCTor()
        {
            /// Get render info back, but make sure that nothing
            /// actually gets back
            /// 
            callbackWatcher cbw = new callbackWatcher();
            using (DTRenderClient rc = new DTRenderClient(new InstanceContext(cbw)))
            {
            }
            Assert.IsFalse(cbw.WasCalled, "Seems like the callback was called!");
        }

        [TestMethod]
        public void TestCallbackQueuedAfterConnect()
        {
            callbackWatcher cbw = new callbackWatcher();
            using (DTRenderClient rc = new DTRenderClient(new InstanceContext(cbw)))
            {
                rc.ReadyForWork();
                using (DTDBClient dc = new DTDBClient())
                {
                    Assert.IsFalse(cbw.WasCalled, "Nothign queued, should not have been called yet!");
                    string a = GetTalkXML();
                    dc.SubmitNewTalk(a, "Talk 1", DateTime.Now);
                    Assert.IsTrue(cbw.WaitOnEvent(15 * 1000), "Failed to get callback!");
                    Assert.IsTrue(cbw.WasCalled, "Callback was not triggered!");
                    Thread.Sleep(500);
                }
            }
        }

        [TestMethod]
        public void TestCallbackQueuedBeforeConnect()
        {
            callbackWatcher cbw = new callbackWatcher();
            using (DTRenderClient rc = new DTRenderClient(new InstanceContext(cbw)))
            {
                using (DTDBClient dc = new DTDBClient())
                {
                    Assert.IsFalse(cbw.WasCalled, "Nothign queued, should not have been called yet!");
                    string a = GetTalkXML();
                    dc.SubmitNewTalk(a, "Talk 1", DateTime.Now);
                }
                rc.ReadyForWork();
                Assert.IsTrue(cbw.WaitOnEvent(15 * 1000), "Failed to get callback!");
                Assert.IsTrue(cbw.WasCalled, "Callback was not triggered!");
                Thread.Sleep(1000);
                Assert.AreEqual(1, cbw.Count, "The callback seems to have been called more than once!");
            }
        }

        [TestMethod]
        public void TestCallbackQueuededMultipleTalks()
        {
            callbackWatcher cbw = new callbackWatcher();
            using (DTRenderClient rc = new DTRenderClient(new InstanceContext(cbw)))
            {
                using (DTDBClient dc = new DTDBClient())
                {
                    Assert.IsFalse(cbw.WasCalled, "Nothign queued, should not have been called yet!");
                    dc.SubmitNewTalk(GetTalkXML("dude1"), "Talk 1", DateTime.Now);
                    dc.SubmitNewTalk(GetTalkXML("dude2"), "Talk 2", DateTime.Now);
                    dc.SubmitNewTalk(GetTalkXML("dude3"), "Talk 3", DateTime.Now);
                }
                rc.ReadyForWork();
                Assert.IsTrue(cbw.WaitOnEvent(15 * 1000), "Failed to get callback!");
                Assert.IsTrue(cbw.WasCalled, "Callback was not triggered!");
                Thread.Sleep(1000);
                Assert.AreEqual(1, cbw.Count, "The callback seems to have been called more than once!");
            }
        }

        /// <summary>
        ///  Seems to cause a crash when we pop out of the using so quickly -- like the communication channel wasn't finished yet or something.
        /// </summary>
        [TestMethod]
        public void TestCallbackQueuedBeforeConnectCrash()
        {
            callbackWatcher cbw = new callbackWatcher();
            using (DTRenderClient rc = new DTRenderClient(new InstanceContext(cbw)))
            {
                using (DTDBClient dc = new DTDBClient())
                {
                    Assert.IsFalse(cbw.WasCalled, "Nothign queued, should not have been called yet!");
                    string a = GetTalkXML();
                    dc.SubmitNewTalk(a, "Talk 1", DateTime.Now);
                    dc.Close();
                }
                rc.ReadyForWork();
                Assert.IsTrue(cbw.WaitOnEvent(15 * 1000), "Failed to get callback!");
                Assert.IsTrue(cbw.WasCalled, "Callback was not triggered!");

                /// If this sleep isn't in here, we get a crash. No idea why!
                /// Thread.Sleep(1000);
                rc.Close();
            }
        }

        /// <summary>
        /// Render callback that will wait until "signaled" before
        /// marking a talk as done.
        /// </summary>
        class callbackAndHold : ServiceReference1.IDTRenderCallback
        {
            public callbackAndHold()
            {
                TalksRendered = 1;
            }

            private AutoResetEvent evt = new AutoResetEvent(false);
            public void Set()
            {
                evt.Set();
            }

            public int TalksRendered { get; set; }

            public DTRenderClient Render { get; set; }

            private AutoResetEvent calledevt = new AutoResetEvent(false);
            public void WaitForCall()
            {
                if (!calledevt.WaitOne(1000 * 20))
                {
                    throw new System.Exception("Waiting for call failed to complete");
                }
            }

            private AutoResetEvent alldoneevt = new AutoResetEvent(false);
            public void WaitForDone()
            {
                if (!alldoneevt.WaitOne(1000 * 20))
                {
                    throw new System.Exception("Waiting for done failed to complete");
                }
            }

            public bool RenderTalk(DeepTalkDBServiceTest.ServiceReference1.DTRenderTalkInfo info)
            {
                calledevt.Set();
                ThreadPool.QueueUserWorkItem(arg =>
                {
                    evt.WaitOne();
                    Render.RenderDone(info.ID, (float)100.0, TalksRendered);
                    alldoneevt.Set();
                });
                return true;
            }
        }

        [TestMethod]
        public void TestMarkRendered()
        {
            using (DTDBClient rc = new DTDBClient())
            {
                rc.SubmitNewTalk(GetTalkXML(), "Talk 1", DateTime.Now);
                BasicTalkInfo[] talks = rc.GetRenderedTalks(10);
                Assert.IsTrue(talks.Length == 0, "There should be no rendered talks right now!");
                talks = rc.GetUnrenderedTalks(10);
                Assert.IsTrue(talks.Length == 1, "There should be one unrendered talk!");

                callbackAndHold cb = new callbackAndHold();
                using (DTRenderClient render = new DTRenderClient(new InstanceContext(cb)))
                {
                    cb.Render = render;
                    render.ReadyForWork();
                    /// Wait until the rendering is started...
                    cb.WaitForCall();

                    talks = rc.GetUnrenderedTalks(10);
                    Assert.IsTrue(talks.Length == 1, "There should be one unrendered talk while a talk is being rendered!");
                    talks = rc.GetRenderedTalks(10);
                    Assert.IsTrue(talks.Length == 0, "There should be no rendered talks!");

                    /// Have the render finish, and wait for that to actually happen
                    cb.Set();
                    cb.WaitForDone();
                }

                talks = rc.GetRenderedTalks(10);
                Assert.AreEqual(1, talks.Length, "There should be one rendered talk");
                talks = rc.GetUnrenderedTalks(10);
                Assert.AreEqual(0, talks.Length, "There should be no unrendered talks!");
            }
        }

        [TestMethod]
        public void TestMarkRenderedZeroTalksRendered()
        {
            using (DTDBClient rc = new DTDBClient())
            {
                rc.SubmitNewTalk(GetTalkXML(), "Talk 1", DateTime.Now);
                BasicTalkInfo[] talks = rc.GetRenderedTalks(10);
                Assert.IsTrue(talks.Length == 0, "There should be no rendered talks right now!");
                talks = rc.GetUnrenderedTalks(10);
                Assert.IsTrue(talks.Length == 1, "There should be one unrendered talk!");

                callbackAndHold cb = new callbackAndHold();
                cb.TalksRendered = 0;
                using (DTRenderClient render = new DTRenderClient(new InstanceContext(cb)))
                {
                    cb.Render = render;
                    render.ReadyForWork();

                    /// Have the render finish, and wait for that to actually happen
                    cb.Set();
                    cb.WaitForDone();
                }

                talks = rc.GetRenderedTalks(10);
                Assert.AreEqual(0, talks.Length, "There should be zero rendered talks (the rendered one has zero talks in it)");
                talks = rc.GetUnrenderedTalks(10);
                Assert.AreEqual(0, talks.Length, "There should be no unrendered talks!");
            }
        }

        /// <summary>
        /// Simple render-guy callback.
        /// </summary>
        class callbackGO : ServiceReference1.IDTRenderCallback
        {
            public DTRenderClient Render { get; set; }

            private AutoResetEvent alldoneevt = new AutoResetEvent(false);
            public void WaitForDone()
            {
                if (!alldoneevt.WaitOne(1000 * 20))
                    throw new System.Exception("Failed while wiating for all done event!");

            }

            public bool RenderTalk(DeepTalkDBServiceTest.ServiceReference1.DTRenderTalkInfo info)
            {
                ThreadPool.QueueUserWorkItem(arg =>
                {
                    Render.RenderDone(info.ID, (float)100.0, 1);
                    alldoneevt.Set();
                });
                return true;
            }
        }

        /// <summary>
        /// This test sometimes fails due to database latency -- the DB doesn't get updated in time for the below
        /// guy to be run. As if changes were flushed out to the database itself.
        /// </summary>
        [TestMethod]
        public void TestRenderedTalkIsDisplayable()
        {
            using (DTDBClient rc = new DTDBClient())
            {
                string agendaXML = GetTalkXML();
                rc.SubmitNewTalk(agendaXML, "Talk 1", DateTime.Now);
                int id = rc.FindTalk(agendaXML);

                callbackGO cb = new callbackGO();
                using (DTRenderClient render = new DTRenderClient(new InstanceContext(cb)))
                {
                    cb.Render = render;
                    Assert.IsFalse(rc.CanBeViewed(id), "Talk should not be viewable right now");
                    render.ReadyForWork();
                    /// Wait until the rendering is done...
                    cb.WaitForDone();
                }

                Assert.IsTrue(rc.CanBeViewed(id), "Talk is rendered and should have been viewable!");
            }
        }

        [TestMethod]
        public void TestRendredTalkCanBeUnrendered()
        {
            using (DTDBClient rc = new DTDBClient())
            {
                string agendaXML = GetTalkXML();
                rc.SubmitNewTalk(agendaXML, "Talk 1", DateTime.Now);
                int id = rc.FindTalk(agendaXML);

                callbackGO cb = new callbackGO();
                using (DTRenderClient render = new DTRenderClient(new InstanceContext(cb)))
                {
                    cb.Render = render;
                    render.ReadyForWork();
                    cb.WaitForDone();
                }
                Assert.IsTrue(rc.CanBeViewed(id), "Talk is rendered and should have been viewable!");

                /// Now, mark the talk as needing to be rendered again, and make sure that is the case!

                rc.ResetTalkStateToUnrendered(id);
                Assert.IsFalse(rc.CanBeViewed(id), "Talk should no longer be visible!");
            }
        }

        /// <summary>
        /// Simple render-guy callback - only we never finish the rendering. Simulate resetting it. :-)
        /// </summary>
        class callbackLeave : ServiceReference1.IDTRenderCallback
        {
            public DTRenderClient Render { get; set; }

            private AutoResetEvent alldoneevt = new AutoResetEvent(false);
            public void WaitForDone()
            {
                if (!alldoneevt.WaitOne(1000 * 10))
                    throw new System.Exception("Failed while wiating for all done event!");

            }

            public bool RenderTalk(DeepTalkDBServiceTest.ServiceReference1.DTRenderTalkInfo info)
            {
                ThreadPool.QueueUserWorkItem(arg =>
                {
                    alldoneevt.Set();
                });
                return true;
            }
        }

        [TestMethod]
        public void TestResetRenderingTalks()
        {
            using (DTDBClient rc = new DTDBClient())
            {
                string agendaXML = GetTalkXML();
                rc.SubmitNewTalk(agendaXML, "Talk 1", DateTime.Now);
                int id = rc.FindTalk(agendaXML);

                callbackLeave cbleave = new callbackLeave();
                using (DTRenderClient render = new DTRenderClient(new InstanceContext(cbleave)))
                {
                    cbleave.Render = render;
                    render.ReadyForWork();
                    cbleave.WaitForDone();
                }

                /// At this point we have a talk marked as rendering. Reset things and try running again.

                int[] numreset = rc.ResetRenderingTalks();

                using (DTRenderClient render = new DTRenderClient(new InstanceContext(cbleave)))
                {
                    cbleave.Render = render;
                    render.ReadyForWork();
                    cbleave.WaitForDone();
                    Thread.Sleep(500);
                }

                Assert.IsNotNull(numreset, "Wasn't expecting a null list back of talk id's!");
                Assert.AreEqual(1, numreset.Length, "Expected to reset one talk");
                Assert.AreEqual(id, numreset[0], "Talk ID shoudl match the talk ID that we are dealing with here!");
            }
        }

        [TestMethod]
        public void TestUpdateConferenceData()
        {
            using (DTDBClient rc = new DTDBClient())
            {
                string agendaXML = GetTalkXML();
                rc.SubmitNewTalk(agendaXML, "Talk 1", DateTime.Now);
                int id = rc.FindTalk(agendaXML);

                callbackGO cb = new callbackGO();
                using (DTRenderClient render = new DTRenderClient(new InstanceContext(cb)))
                {
                    render.UpdateConferenceData(id, "Dude", new DateTime(2008, 05, 05), new DateTime(2008, 05, 06));
                }

                BasicTalkInfo[] info = rc.GetUnrenderedTalks(10);
                Assert.IsTrue(info.Length == 1, "Should be one talk unrendered!");
                Assert.AreEqual("Dude", info[0].Title, "Talk title didn't get reset");
                Assert.AreEqual(new DateTime(2008, 05, 05), info[0].StartDate, "Talk start time isn't right");
                Assert.AreEqual(new DateTime(2008, 05, 06), rc.GetConferenceEndDate(id), "Talk end time isn't right");
            }
        }
    }
}
